package com.swdegao.iot.netty.server.qianbaotongplushandler;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.binary.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Arrays;

/**
 * 千宝通通协议
 *
 * @author TsingYu
 * @version 1.0
 * @date 2024/10/25 14:33
 */
@SuppressWarnings("unused")
public class QianBaoTong {

    /**
     * 数据帧头
     */
    private final static byte[] FRAME_HEADER = {0x7B, 0x01, 0x00, 0x16};

    /**
     * 发送数据包头
     */
    private final static byte[] SENDING_PACKAGE_HEADER = {0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x53};

    /**
     * 应答数据包头
     */
    private final static byte[] RESPONSE_PACKAGE_HEADER = {0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x42};
    /**
     * 结束字符
     */
    private final static byte END_CHARACTER = 0x45;
    /**
     * 6字节数组，用来表示采集器或表地址为空的情况
     */
    private final static byte[] BLANK_WITH_6_BYTES = {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF};

    /**
     * 开阀控制码
     */
    private final static byte[] VALVE_CONTROL_OPEN = {0x4B, 0x4B, 0x4B};
    /**
     * 关阀控制码
     */
    private final static byte[] VALVE_CONTROL_CLOSE = {0x47, 0x47, 0x47};
    /**
     * 开启采集器通道控制码
     */
    private final static byte[] COLLECTOR_CONTROL_OPEN = {0x4F, 0x4F, 0x4F};
    /**
     * 关闭采集器通道控制码
     */
    private final static byte[] COLLECTOR_CONTROL_CLOSE = {0x43, 0x43, 0x43};

    private final static char[] HA00 = {
            0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1,
            0x12, 0x02, 0x32, 0x22, 0x52, 0x42, 0x72, 0x62, 0x93, 0x83, 0xB3, 0xA3, 0xD3, 0xC3, 0xF3, 0xE3,
            0x24, 0x34, 0x04, 0x14, 0x64, 0x74, 0x44, 0x54, 0xA5, 0xB5, 0x85, 0x95, 0xE5, 0xF5, 0xC5, 0xD5,
            0x36, 0x26, 0x16, 0x06, 0x76, 0x66, 0x56, 0x46, 0xB7, 0xA7, 0x97, 0x87, 0xF7, 0xE7, 0xD7, 0xC7,
            0x48, 0x58, 0x68, 0x78, 0x08, 0x18, 0x28, 0x38, 0xC9, 0xD9, 0xE9, 0xF9, 0x89, 0x99, 0xA9, 0xB9,
            0x5A, 0x4A, 0x7A, 0x6A, 0x1A, 0x0A, 0x3A, 0x2A, 0xDB, 0xCB, 0xFB, 0xEB, 0x9B, 0x8B, 0xBB, 0xAB,
            0x6C, 0x7C, 0x4C, 0x5C, 0x2C, 0x3C, 0x0C, 0x1C, 0xED, 0xFD, 0xCD, 0xDD, 0xAD, 0xBD, 0x8D, 0x9D,
            0x7E, 0x6E, 0x5E, 0x4E, 0x3E, 0x2E, 0x1E, 0x0E, 0xFF, 0xEF, 0xDF, 0xCF, 0xBF, 0xAF, 0x9F, 0x8F,
            0x91, 0x81, 0xB1, 0xA1, 0xD1, 0xC1, 0xF1, 0xE1, 0x10, 0x00, 0x30, 0x20, 0x50, 0x40, 0x70, 0x60,
            0x83, 0x93, 0xA3, 0xB3, 0xC3, 0xD3, 0xE3, 0xF3, 0x02, 0x12, 0x22, 0x32, 0x42, 0x52, 0x62, 0x72,
            0xB5, 0xA5, 0x95, 0x85, 0xF5, 0xE5, 0xD5, 0xC5, 0x34, 0x24, 0x14, 0x04, 0x74, 0x64, 0x54, 0x44,
            0xA7, 0xB7, 0x87, 0x97, 0xE7, 0xF7, 0xC7, 0xD7, 0x26, 0x36, 0x06, 0x16, 0x66, 0x76, 0x46, 0x56,
            0xD9, 0xC9, 0xF9, 0xE9, 0x99, 0x89, 0xB9, 0xA9, 0x58, 0x48, 0x78, 0x68, 0x18, 0x08, 0x38, 0x28,
            0xCB, 0xDB, 0xEB, 0xFB, 0x8B, 0x9B, 0xAB, 0xBB, 0x4A, 0x5A, 0x6A, 0x7A, 0x0A, 0x1A, 0x2A, 0x3A,
            0xFD, 0xED, 0xDD, 0xCD, 0xBD, 0xAD, 0x9D, 0x8D, 0x7C, 0x6C, 0x5C, 0x4C, 0x3C, 0x2C, 0x1C, 0x0C,
            0xEF, 0xFF, 0xCF, 0xDF, 0xAF, 0xBF, 0x8F, 0x9F, 0x6E, 0x7E, 0x4E, 0x5E, 0x2E, 0x3E, 0x0E, 0x1E
    };

    private final static char[] LA00 = {
            0x00, 0x21, 0x42, 0x63, 0x84, 0xA5, 0xC6, 0xE7, 0x08, 0x29, 0x4A, 0x6B, 0x8C, 0xAD, 0xCE, 0xEF,
            0x31, 0x10, 0x73, 0x52, 0xB5, 0x94, 0xF7, 0xD6, 0x39, 0x18, 0x7B, 0x5A, 0xBD, 0x9C, 0xFF, 0xDE,
            0x62, 0x43, 0x20, 0x01, 0xE6, 0xC7, 0xA4, 0x85, 0x6A, 0x4B, 0x28, 0x09, 0xEE, 0xCF, 0xAC, 0x8D,
            0x53, 0x72, 0x11, 0x30, 0xD7, 0xF6, 0x95, 0xB4, 0x5B, 0x7A, 0x19, 0x38, 0xDF, 0xFE, 0x9D, 0xBC,
            0xC4, 0xE5, 0x86, 0xA7, 0x40, 0x61, 0x02, 0x23, 0xCC, 0xED, 0x8E, 0xAF, 0x48, 0x69, 0x0A, 0x2B,
            0xF5, 0xD4, 0xB7, 0x96, 0x71, 0x50, 0x33, 0x12, 0xFD, 0xDC, 0xBF, 0x9E, 0x79, 0x58, 0x3B, 0x1A,
            0xA6, 0x87, 0xE4, 0xC5, 0x22, 0x03, 0x60, 0x41, 0xAE, 0x8F, 0xEC, 0xCD, 0x2A, 0x0B, 0x68, 0x49,
            0x97, 0xB6, 0xD5, 0xF4, 0x13, 0x32, 0x51, 0x70, 0x9F, 0xBE, 0xDD, 0xFC, 0x1B, 0x3A, 0x59, 0x78,
            0x88, 0xA9, 0xCA, 0xEB, 0x0C, 0x2D, 0x4E, 0x6F, 0x80, 0xA1, 0xC2, 0xE3, 0x04, 0x25, 0x46, 0x67,
            0xB9, 0x98, 0xFB, 0xDA, 0x3D, 0x1C, 0x7F, 0x5E, 0xB1, 0x90, 0xF3, 0xD2, 0x35, 0x14, 0x77, 0x56,
            0xEA, 0xCB, 0xA8, 0x89, 0x6E, 0x4F, 0x2C, 0x0D, 0xE2, 0xC3, 0xA0, 0x81, 0x66, 0x47, 0x24, 0x05,
            0xDB, 0xFA, 0x99, 0xB8, 0x5F, 0x7E, 0x1D, 0x3C, 0xD3, 0xF2, 0x91, 0xB0, 0x57, 0x76, 0x15, 0x34,
            0x4C, 0x6D, 0x0E, 0x2F, 0xC8, 0xE9, 0x8A, 0xAB, 0x44, 0x65, 0x06, 0x27, 0xC0, 0xE1, 0x82, 0xA3,
            0x7D, 0x5C, 0x3F, 0x1E, 0xF9, 0xD8, 0xBB, 0x9A, 0x75, 0x54, 0x37, 0x16, 0xF1, 0xD0, 0xB3, 0x92,
            0x2E, 0x0F, 0x6C, 0x4D, 0xAA, 0x8B, 0xE8, 0xC9, 0x26, 0x07, 0x64, 0x45, 0xA2, 0x83, 0xE0, 0xC1,
            0x1F, 0x3E, 0x5D, 0x7C, 0x9B, 0xBA, 0xD9, 0xF8, 0x17, 0x36, 0x55, 0x74, 0x93, 0xB2, 0xD1, 0xF0
    };

    /**
     * crc校验和
     *
     * @param bytes bytes
     */
    public static byte[] crc188(byte[] bytes) {
        byte[] result = new byte[2];
        int a, b, c;
        a = bytes[0] & 0xff;
        b = bytes[1] & 0xff;
        c = bytes[2] & 0xff;
        c = (LA00[a] ^ c);
        a = (HA00[a] ^ b);
        b = c;
        for (int i = 3; i < bytes.length; i++) {
            c = bytes[i] & 0xff;
            c = (LA00[a] ^ c);
            a = (HA00[a] ^ b);
            b = c;
        }
        result[0] = (byte) a;
        result[1] = (byte) b;
        return result;
    }

    /**
     * 是否为合法帧
     *
     * @param frame 待校验帧数据
     * @return 是否合法
     */
    public static boolean isValidFrame(Object frame) {
        byte[] frameByteArray = objectToByteArray(frame);
        if (frameByteArray == null) {
            return false;
        }
        // 检查帧头是否相等
        byte[] headerFrame = Arrays.copyOfRange(frameByteArray, 0, FRAME_HEADER.length);
        if (!Arrays.equals(headerFrame, FRAME_HEADER)) {
            return false;
        }
        // 数据域
        byte[] dataFrame = Arrays.copyOfRange(frameByteArray, 25, frameByteArray.length - 3);
        // CRC校验码
        byte[] crcFrame = Arrays.copyOfRange(frameByteArray, frameByteArray.length - 3, frameByteArray.length - 1);
        // 计算出的CRC
        byte[] crc = crc188(dataFrame);
        return Arrays.equals(crcFrame, crc);
    }

    /**
     * 主动上报心跳数据解析
     *
     * @return 集中器地址
     */
    public static String heartBeat(Object object) {
        byte[] frame = objectToByteArray(object);
        byte[] addressFrame = Arrays.copyOfRange(frame, 4, 15);
        return StringUtils.newStringUsAscii(addressFrame);
    }

    /**
     * 编码采集命令
     *
     * @param concentratorAddress 集中器地址
     * @param collectorAddress    采集器地址
     * @param meterAddress        表地址（表地址不为空时，采集器地址也不能为空）
     * @return 采集命令字节数组
     */
    public static byte[] encodingCollectingCommand(String concentratorAddress, String collectorAddress, String meterAddress) {
        if (org.apache.commons.lang3.StringUtils.isBlank(concentratorAddress)) {
            throw new RuntimeException("集中器地址不能为空");
        }
        if (org.apache.commons.lang3.StringUtils.isBlank(collectorAddress) && org.apache.commons.lang3.StringUtils.isNotBlank(meterAddress)) {
            throw new RuntimeException("表地址不为空时，采集器地址也不能为空");
        }
        // 控制码
        byte[] controlCode = {0x4D, 0x4D, 0x4D};
        byte[] result = new byte[43];
        System.arraycopy(FRAME_HEADER, 0, result, 0, FRAME_HEADER.length);
        byte[] concentratorAddressAscii = StringUtils.getBytesUsAscii(concentratorAddress);
        System.arraycopy(concentratorAddressAscii, 0, result, 4, concentratorAddressAscii.length);
        System.arraycopy(SENDING_PACKAGE_HEADER, 0, result, 15, SENDING_PACKAGE_HEADER.length);
        System.arraycopy(controlCode, 0, result, 25, controlCode.length);
        if (org.apache.commons.lang3.StringUtils.isBlank(collectorAddress)) {
            System.arraycopy(BLANK_WITH_6_BYTES, 0, result, 28, BLANK_WITH_6_BYTES.length);
        } else {
            byte[] collectorAddressArray = convertToBcd(collectorAddress);
            byte[] collectorAddressArrayReversed = reverse(collectorAddressArray);
            System.arraycopy(collectorAddressArrayReversed, 0, result, 28, collectorAddressArrayReversed.length);
        }
        if (org.apache.commons.lang3.StringUtils.isBlank(meterAddress)) {
            System.arraycopy(BLANK_WITH_6_BYTES, 0, result, 34, BLANK_WITH_6_BYTES.length);
        } else {
            byte[] collectorAddressArray = convertToBcd(meterAddress);
            byte[] collectorAddressArrayReversed = reverse(collectorAddressArray);
            System.arraycopy(collectorAddressArrayReversed, 0, result, 34, collectorAddressArrayReversed.length);
        }
        byte[] crc = crc188(Arrays.copyOfRange(result, 25, 40));
        System.arraycopy(crc, 0, result, 40, crc.length);
        result[result.length - 1] = END_CHARACTER;
        return result;
    }

    /**
     * 解析采集应答
     *
     * @param object 待解析对象
     * @return 解析出的信息
     */
    public static QianBaoTongModel decodeCollectingResponse(Object object) {
        QianBaoTongModel model = new QianBaoTongModel();
        byte[] response = objectToByteArray(object);
        byte[] identifiesPacketHeaders = Arrays.copyOfRange(response, 0, 4);
        model.setIdentifiesPacketHeaders(identifiesPacketHeaders);
        byte[] concentratorAddrRaw = Arrays.copyOfRange(response, 4, 15);
        model.setConcentratorAddrRaw(concentratorAddrRaw);
        model.setConcentratorAddr(StringUtils.newStringUsAscii(concentratorAddrRaw));
        byte[] dataPacketHeaders = Arrays.copyOfRange(response, 15, 25);
        model.setDataPacketHeaders(dataPacketHeaders);
        byte[] controlCode = Arrays.copyOfRange(response, 25, 28);
        model.setControlCode(controlCode);
        byte[] collectorAddrRaw = Arrays.copyOfRange(response, 28, 34);
        model.setCollectorAddrRaw(collectorAddrRaw);
        model.setCollectorAddr(Hex.encodeHexString(reverse(collectorAddrRaw)));
        byte[] meterAddrRaw = Arrays.copyOfRange(response, 34, 40);
        model.setMeterAddrRaw(meterAddrRaw);
        model.setMeterAddr(Hex.encodeHexString(reverse(meterAddrRaw)));
        byte[] crc = Arrays.copyOfRange(response, response.length - 3, response.length - 1);
        model.setCrc(crc);
        model.setEndChar(response[response.length - 1]);
        // 是否包含表读数数据
        boolean includingReadingData = identifiesPacketHeaders.length + concentratorAddrRaw.length + dataPacketHeaders.length
                + controlCode.length + collectorAddrRaw.length + meterAddrRaw.length + crc.length + 1 < response.length;
        if (includingReadingData) {
            byte[] meterReadingNumberRaw = Arrays.copyOfRange(response, 40, 44);
            model.setMeterReadingNumberRaw(meterReadingNumberRaw);
            model.setMeterReadingNumber((new BigDecimal(Hex.encodeHexString(reverse(meterReadingNumberRaw)))).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP));
            byte[] meterStatus = Arrays.copyOfRange(response, 44, 45);
            model.setMeterStatus(QianBaoTongModel.CommunicationStatus.getStatus(meterStatus[0]));
            model.setMeterStatusRaw(meterStatus[0]);
        }
        return model;
    }

    /**
     * 编码读取命令
     *
     * @param concentratorAddr 集中器地址
     * @param pageNum          页码
     * @return 读取命令字节数组
     */
    public static byte[] encodeReadingCommand(String concentratorAddr, byte pageNum) {
        if (org.apache.commons.lang3.StringUtils.isBlank(concentratorAddr)) {
            throw new RuntimeException("集中器地址不能为空");
        }
        // 控制码
        byte[] controlCode = {0x52, 0x52, 0x52};
        byte[] result = new byte[32];
        System.arraycopy(FRAME_HEADER, 0, result, 0, FRAME_HEADER.length);
        byte[] concentratorAddressAscii = StringUtils.getBytesUsAscii(concentratorAddr);
        System.arraycopy(concentratorAddressAscii, 0, result, 4, concentratorAddressAscii.length);
        System.arraycopy(SENDING_PACKAGE_HEADER, 0, result, 15, SENDING_PACKAGE_HEADER.length);
        System.arraycopy(controlCode, 0, result, 25, controlCode.length);
        result[28] = pageNum;
        byte[] crc = crc188(Arrays.copyOfRange(result, 25, 29));
        System.arraycopy(crc, 0, result, 29, crc.length);
        result[31] = END_CHARACTER;
        return result;
    }

    /**
     * 解析读取应答（读取、集中器读取、下载档案）
     *
     * @param object         待解析对象
     * @param isConcentrator 是否是集中器信息（集中器读取应答解析时应为true）
     * @param isIncludeData  是否包含数据部分（下载档案应答不包含数据，解析时应为false）
     * @return 解析出的信息
     */
    public static QianBaoTongModel decodeReadingResponse(Object object, boolean isConcentrator, boolean isIncludeData) {
        QianBaoTongModel model = new QianBaoTongModel();
        byte[] response = objectToByteArray(object);
        byte[] identifiesPacketHeaders = Arrays.copyOfRange(response, 0, 4);
        model.setIdentifiesPacketHeaders(identifiesPacketHeaders);
        byte[] concentratorAddrRaw = Arrays.copyOfRange(response, 4, 15);
        model.setConcentratorAddrRaw(concentratorAddrRaw);
        model.setConcentratorAddr(StringUtils.newStringUsAscii(concentratorAddrRaw));
        byte[] dataPacketHeaders = Arrays.copyOfRange(response, 15, 25);
        model.setDataPacketHeaders(dataPacketHeaders);
        byte[] controlCode = Arrays.copyOfRange(response, 25, 28);
        model.setControlCode(controlCode);
        byte pageNum = response[28];
        model.setPageNum(pageNum);
        if (isIncludeData) {
            // 数据部分
            byte[] dataFrag = Arrays.copyOfRange(response, 29, 413);
            if (isConcentrator) {
                model.setConcentratorRaw(dataFrag);
                model.setConcentrator(extractConcentrator(dataFrag));
            } else if (pageNum == 1) {
                // 读取第1页：采集器编号信息
                model.setCollectorAddressesRaw(dataFrag);
                model.setCollectorAddresses(extractAddresses(dataFrag));
            } else {
                // 读取第2页：表信息
                model.setMetersRaw(dataFrag);
                model.setMeters(extractMeters(dataFrag));
            }
        }
        byte[] crc = Arrays.copyOfRange(response, response.length - 3, response.length - 1);
        model.setCrc(crc);
        model.setEndChar(response[response.length - 1]);

        return model;
    }

    /**
     * {@link Object}转字节数组
     *
     * @param object 可以是字节数组，也可以是16进制字符串（必须为偶数长度的字符串）
     * @return 转换成功返回字节数组，失败返回<code>null</code>
     */
    public static byte[] objectToByteArray(Object object) {
        if (object instanceof String) {
            try {
                return Hex.decodeHex((String) object);
            } catch (DecoderException e) {
                return null;
            }
        } else if (object instanceof byte[]) {
            return (byte[]) object;
        } else {
            return null;
        }
    }

    /**
     * 编码集中器设置命令
     *
     * @param oldConcentratorAddr 旧的集中器地址
     * @param newConcentratorAddr 新的集中器地址
     * @return 集中器设置命令字节数组
     */
    public static byte[] encodeConcentratorSettingCommand(String oldConcentratorAddr, String newConcentratorAddr) {
        if (org.apache.commons.lang3.StringUtils.isAnyBlank(oldConcentratorAddr, newConcentratorAddr)) {
            throw new RuntimeException("新旧集中器地址均不能为空");
        }
        // 控制码
        byte[] controlCode = {0x4E, 0x4E, 0x4E};
        byte[] result = new byte[159];
        String dataHexStr = String.format("BCAFD6D0C6F720202020A3BA%s0D0AB0E6B1BE202020202020A3BA0D0AB3F6B3A7C8D5C6DA2020A3BA0D0A000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", Hex.encodeHexString(StringUtils.getBytesUsAscii(newConcentratorAddr)));
        byte[] dataHex = objectToByteArray(dataHexStr);
        System.arraycopy(FRAME_HEADER, 0, result, 0, FRAME_HEADER.length);
        byte[] concentratorAddressAscii = StringUtils.getBytesUsAscii(oldConcentratorAddr);
        System.arraycopy(concentratorAddressAscii, 0, result, 4, concentratorAddressAscii.length);
        System.arraycopy(SENDING_PACKAGE_HEADER, 0, result, 15, SENDING_PACKAGE_HEADER.length);
        System.arraycopy(controlCode, 0, result, 25, controlCode.length);
        System.arraycopy(dataHex, 0, result, 28, dataHex.length);
        byte[] crc = crc188(Arrays.copyOfRange(result, 25, 156));
        System.arraycopy(crc, 0, result, 156, crc.length);
        result[158] = END_CHARACTER;
        return result;
    }

    /**
     * 解析设置应答（集中器设置、IP端口设置）
     *
     * @param object 待解析对象
     * @return 解析出的信息
     */
    public static QianBaoTongModel decodeSettingResponse(Object object) {
        QianBaoTongModel model = new QianBaoTongModel();
        byte[] response = objectToByteArray(object);
        byte[] identifiesPacketHeaders = Arrays.copyOfRange(response, 0, 4);
        model.setIdentifiesPacketHeaders(identifiesPacketHeaders);
        byte[] concentratorAddrRaw = Arrays.copyOfRange(response, 4, 15);
        model.setConcentratorAddrRaw(concentratorAddrRaw);
        model.setConcentratorAddr(StringUtils.newStringUsAscii(concentratorAddrRaw));
        byte[] dataPacketHeaders = Arrays.copyOfRange(response, 15, 25);
        model.setDataPacketHeaders(dataPacketHeaders);
        byte[] controlCode = Arrays.copyOfRange(response, 25, 28);
        model.setControlCode(controlCode);
        byte[] crc = Arrays.copyOfRange(response, 28, 30);
        model.setCrc(crc);
        model.setEndChar(response[30]);
        return model;
    }

    /**
     * 编码ip端口设置命令
     *
     * @param concentratorAddr 集中器地址
     * @param ipPort           IP端口
     * @return ip端口设置命令字节数组
     */
    public static byte[] encodeIpPortSettingCommand(String concentratorAddr, String ipPort) {
        ipPort = ipPort.trim();
        if (org.apache.commons.lang3.StringUtils.isAnyBlank(concentratorAddr, ipPort)) {
            throw new RuntimeException("集中器地址、IP端口均不能为空");
        }
        // 控制码
        byte[] controlCode = {0x41, 0x41, 0x41};
        // ip端口后缀
        byte[] ipPortSuffix = {0x0D, 0x0A};
        byte[] result = new byte[63];
        System.arraycopy(FRAME_HEADER, 0, result, 0, FRAME_HEADER.length);
        byte[] concentratorAddressAscii = StringUtils.getBytesUsAscii(concentratorAddr);
        System.arraycopy(concentratorAddressAscii, 0, result, 4, concentratorAddressAscii.length);
        System.arraycopy(SENDING_PACKAGE_HEADER, 0, result, 15, SENDING_PACKAGE_HEADER.length);
        System.arraycopy(controlCode, 0, result, 25, controlCode.length);
        // ip端口去掉.和:的长度，并把它当作16进制数字
        int ipPortLength = ipPort.length() - 4;
        // 再将ipPortLength这个数字转成10进制存储起来
        result[28] = (byte) Integer.parseInt(String.valueOf(ipPortLength), 16);
        byte[] ipPortAscii = StringUtils.getBytesUsAscii(ipPort);
        System.arraycopy(ipPortAscii, 0, result, 29, ipPortAscii.length);
        System.arraycopy(ipPortSuffix, 0, result, 29 + ipPortAscii.length, ipPortSuffix.length);
        byte[] crc = crc188(Arrays.copyOfRange(result, 25, 60));
        System.arraycopy(crc, 0, result, 60, crc.length);
        result[62] = END_CHARACTER;
        return result;
    }

    /**
     * 编码时钟设置命令
     *
     * @param concentratorAddr 集中器地址
     * @param dateTime         日期时间，yyyyMMddHHmmss格式
     * @return 时钟设置命令字节数组
     */
    public static byte[] encodeClockSettingCommand(String concentratorAddr, String dateTime) {
        if (org.apache.commons.lang3.StringUtils.isAnyBlank(concentratorAddr, dateTime)) {
            throw new RuntimeException("集中器地址、日期时间均不能为空");
        }
        try {
            LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        } catch (DateTimeParseException e) {
            throw new RuntimeException(String.format("日期时间【%s】格式不正确，必须为yyyyMMddHHmmss格式", dateTime));
        }
        // 控制码
        byte[] controlCode = {0x54, 0x54, 0x54};
        // 日期时间前缀
        String dateTimePrefix = "CT";
        byte[] result = new byte[47];
        System.arraycopy(FRAME_HEADER, 0, result, 0, FRAME_HEADER.length);
        byte[] concentratorAddressAscii = StringUtils.getBytesUsAscii(concentratorAddr);
        System.arraycopy(concentratorAddressAscii, 0, result, 4, concentratorAddressAscii.length);
        System.arraycopy(SENDING_PACKAGE_HEADER, 0, result, 15, SENDING_PACKAGE_HEADER.length);
        System.arraycopy(controlCode, 0, result, 25, controlCode.length);
        byte[] dateTimeAscii = StringUtils.getBytesUsAscii(dateTimePrefix + dateTime);
        System.arraycopy(dateTimeAscii, 0, result, 28, dateTimeAscii.length);
        byte[] crc = crc188(Arrays.copyOfRange(result, 25, 44));
        System.arraycopy(crc, 0, result, 44, crc.length);
        result[46] = END_CHARACTER;
        return result;
    }


    /**
     * 编码定时抄表设置命令
     *
     * @param concentratorAddr 集中器地址
     * @param dateTime         日期时间，yyyyMMddHHmmss格式
     * @return 定时抄表设置命令字节数组
     */
    public static byte[] encodeTimingReadingSettingCommand(String concentratorAddr, String dateTime) {
        // TODO 【数据 时间格式：PT1(Y/N)dd2(Y/N)ddhhmmss Y:表示应用 N:表示无效  每月dd号的hhmmss进行抄表】这部分PT1、dd2含义不明确，还需确认后再开发这部分代码
        if (org.apache.commons.lang3.StringUtils.isAnyBlank(concentratorAddr, dateTime)) {
            throw new RuntimeException("集中器地址、日期时间均不能为空");
        }
        // 控制码
        byte[] controlCode = {0x50, 0x50, 0x50};
        // 日期时间前缀
        String prefix = "PT1(Y/N)dd2(Y/N)";
        byte[] result = new byte[47];
        System.arraycopy(FRAME_HEADER, 0, result, 0, FRAME_HEADER.length);
        byte[] concentratorAddressAscii = StringUtils.getBytesUsAscii(concentratorAddr);
        System.arraycopy(concentratorAddressAscii, 0, result, 4, concentratorAddressAscii.length);
        System.arraycopy(SENDING_PACKAGE_HEADER, 0, result, 15, SENDING_PACKAGE_HEADER.length);
        System.arraycopy(controlCode, 0, result, 25, controlCode.length);
        byte[] timingDataAscii = StringUtils.getBytesUsAscii(prefix + dateTime);
        System.arraycopy(timingDataAscii, 0, result, 28, timingDataAscii.length);
        byte[] crc = crc188(Arrays.copyOfRange(result, 25, 44));
        System.arraycopy(crc, 0, result, 44, crc.length);
        result[46] = END_CHARACTER;
        return result;
    }


    /**
     * 编码下载档案命令
     *
     * @param concentratorAddr   集中器地址
     * @param pageNum            页面号
     * @param collectorAddresses 采集器地址数组
     * @param meters             户表信息，其中户表的采集器索引为采集器地址数组下标
     * @return 下载档案命令字节数组
     */
    public static byte[] encodeDownloadCommand(String concentratorAddr, byte pageNum, String[] collectorAddresses, QianBaoTongModel.Meter[] meters) {
        if (org.apache.commons.lang3.StringUtils.isAnyBlank(concentratorAddr)) {
            throw new RuntimeException("集中器地址不能为空");
        }
        if (collectorAddresses == null || meters == null) {
            throw new RuntimeException("采集器地址、户表信息不能为空");
        }
        // 控制码
        byte[] controlCode = {0x57, 0x57, 0x57};
        byte[] result = new byte[416];
        System.arraycopy(FRAME_HEADER, 0, result, 0, FRAME_HEADER.length);
        byte[] concentratorAddressAscii = StringUtils.getBytesUsAscii(concentratorAddr);
        System.arraycopy(concentratorAddressAscii, 0, result, 4, concentratorAddressAscii.length);
        System.arraycopy(SENDING_PACKAGE_HEADER, 0, result, 15, SENDING_PACKAGE_HEADER.length);
        System.arraycopy(controlCode, 0, result, 25, controlCode.length);
        result[28] = pageNum;
        if (pageNum == 1) {
            byte[] collectorAddressArray = convertAddressesToByteArray(collectorAddresses);
            System.arraycopy(collectorAddressArray, 0, result, 29, collectorAddressArray.length);
        } else {
            byte[] meterArray = convertMetersToByteArray(meters);
            System.arraycopy(meterArray, 0, result, 29, meterArray.length);
        }
        byte[] crc = crc188(Arrays.copyOfRange(result, 25, 413));
        System.arraycopy(crc, 0, result, 413, crc.length);
        result[415] = END_CHARACTER;
        return result;
    }

    /**
     * 编码阀门控制命令
     *
     * @param concentratorAddr 集中器地址
     * @param meterAddr        表地址
     * @param open             开阀<code>true</code>，关阀<code>false</code>
     * @return 阀门控制命令字节数组
     */
    public static byte[] encodeValveControlCommand(String concentratorAddr, String meterAddr, boolean open) {
        if (org.apache.commons.lang3.StringUtils.isAnyBlank(concentratorAddr, meterAddr)) {
            throw new RuntimeException("集中器地址、表地址均不能为空");
        }
        byte[] result = new byte[37];
        setControlCommand(concentratorAddr, meterAddr, result, open ? VALVE_CONTROL_OPEN : VALVE_CONTROL_CLOSE);
        return result;
    }

    /**
     * 编码采集器通道控制命令
     *
     * @param concentratorAddr 集中器地址
     * @param collectorAddr    采集器地址
     * @param open             开启<code>true</code>，关闭<code>false</code>
     * @return 采集器通道控制命令字节数组
     */
    public static byte[] encodeCollectorControlCommand(String concentratorAddr, String collectorAddr, boolean open) {
        if (org.apache.commons.lang3.StringUtils.isAnyBlank(concentratorAddr, collectorAddr)) {
            throw new RuntimeException("集中器地址、采集器地址均不能为空");
        }
        byte[] result = new byte[37];
        setControlCommand(concentratorAddr, collectorAddr, result, open ? COLLECTOR_CONTROL_OPEN : COLLECTOR_CONTROL_CLOSE);
        return result;
    }

    /**
     * 解析控制命令应答（阀控、采集器通道控制）
     *
     * @param object 待解析对象
     * @return 控制命令字节数组
     */
    public static QianBaoTongModel decodeControlResponse(Object object) {
        QianBaoTongModel model = new QianBaoTongModel();
        byte[] response = objectToByteArray(object);
        byte[] identifiesPacketHeaders = Arrays.copyOfRange(response, 0, 4);
        model.setIdentifiesPacketHeaders(identifiesPacketHeaders);
        byte[] concentratorAddrRaw = Arrays.copyOfRange(response, 4, 15);
        model.setConcentratorAddrRaw(concentratorAddrRaw);
        model.setConcentratorAddr(StringUtils.newStringUsAscii(concentratorAddrRaw));
        byte[] dataPacketHeaders = Arrays.copyOfRange(response, 15, 25);
        model.setDataPacketHeaders(dataPacketHeaders);
        byte[] controlCode = Arrays.copyOfRange(response, 25, 28);
        model.setControlCode(controlCode);
        byte[] addrRaw = Arrays.copyOfRange(response, 28, 34);
        String addr = Hex.encodeHexString(reverse(addrRaw));
        if (Arrays.equals(controlCode, VALVE_CONTROL_OPEN) || Arrays.equals(controlCode, VALVE_CONTROL_CLOSE)) {
            model.setMeterAddrRaw(addrRaw);
            model.setMeterAddr(addr);
        } else if (Arrays.equals(controlCode, COLLECTOR_CONTROL_OPEN) || Arrays.equals(controlCode, COLLECTOR_CONTROL_CLOSE)) {
            model.setCollectorAddrRaw(addrRaw);
            model.setCollectorAddr(addr);
        }
        byte[] crc = Arrays.copyOfRange(response, 34, 36);
        model.setCrc(crc);
        model.setEndChar(response[36]);
        return model;
    }

    /**
     * 逆序排列给定字节数组
     *
     * @param byteArray 给定字节数组
     * @return 返回一个新数组
     */
    public static byte[] reverse(byte[] byteArray) {
        byte[] array = byteArray.clone();
        int left = 0;
        int right = array.length - 1;
        while (left < right) {
            byte temp = array[left];
            array[left] = array[right];
            array[right] = temp;
            left++;
            right--;
        }
        return array;
    }

    /**
     * 将数字字符串转换成BCD码字节数组
     *
     * @param input 数字字符串
     * @return BCD码字节数组
     */
    public static byte[] convertToBcd(String input) {
        // 每两个十进制数字组合成一个字节
        int length = input.length();
        // 计算BCD数组的长度
        int bcdLength = (length + 1) / 2;
        byte[] bcdBytes = new byte[bcdLength];

        for (int i = 0; i < length; i++) {
            // 获取当前数字字符
            char digitChar = input.charAt(i);
            // 转换为数字
            int digit = Character.getNumericValue(digitChar);

            // 计算BCD字节位置
            int bcdIndex = i / 2;

            // 如果是偶数位（高4位），则直接设置高位
            if (i % 2 == 0) {
                // 左移4位
                bcdBytes[bcdIndex] = (byte) (digit << 4);
            } else {
                // 设置低位
                bcdBytes[bcdIndex] |= (byte) digit;
            }
        }

        return bcdBytes;
    }

    /**
     * 设置控制命令字节数组
     *
     * @param concentratorAddr 集中器地址
     * @param addr             采集器或者表地址
     * @param result           控制命令字节数组
     * @param controlCode      控制码
     */
    private static void setControlCommand(String concentratorAddr, String addr, byte[] result, byte[] controlCode) {
        System.arraycopy(FRAME_HEADER, 0, result, 0, FRAME_HEADER.length);
        byte[] concentratorAddressAscii = StringUtils.getBytesUsAscii(concentratorAddr);
        System.arraycopy(concentratorAddressAscii, 0, result, 4, concentratorAddressAscii.length);
        System.arraycopy(SENDING_PACKAGE_HEADER, 0, result, 15, SENDING_PACKAGE_HEADER.length);
        System.arraycopy(controlCode, 0, result, 25, controlCode.length);
        byte[] meterAddrByte = reverse(convertToBcd(addr));
        System.arraycopy(meterAddrByte, 0, result, 28, meterAddrByte.length);
        byte[] crc = crc188(Arrays.copyOfRange(result, 25, 34));
        System.arraycopy(crc, 0, result, 34, crc.length);
        result[36] = END_CHARACTER;
    }

    /**
     * 提取采集器地址
     *
     * @param addressArray 采集器地址字节数组
     * @return 采集器地址数组
     */
    private static String[] extractAddresses(byte[] addressArray) {
        // 定义每个地址的长度
        int addressLength = 6;
        int totalAddresses = addressArray.length / addressLength;
        String[] addresses = new String[totalAddresses];
        // 提取地址
        for (int i = 0; i < totalAddresses; i++) {
            addresses[i] = Hex.encodeHexString(reverse(Arrays.copyOfRange(addressArray, i * addressLength, (i + 1) * addressLength)));
        }
        return addresses;
    }

    /**
     * 将采集器地址转换为bcd字节数组
     *
     * @param addressArray 采集器地址数组
     * @return 采集器地址字节数组
     */
    private static byte[] convertAddressesToByteArray(String[] addressArray) {
        // 定义每个地址所占字节
        int addressLength = 6;
        int totalLength = addressArray.length * addressLength;
        byte[] addresses = new byte[totalLength];
        // 转换成字节数组
        for (int i = 0; i < addressArray.length; i++) {
            System.arraycopy(reverse(convertToBcd(addressArray[i])), 0, addresses, i * addressLength, addressLength);
        }
        return addresses;
    }

    /**
     * 提取表信息
     *
     * @param meterArray 表信息字节数组
     * @return 表信息实体类数组
     */
    private static QianBaoTongModel.Meter[] extractMeters(byte[] meterArray) {
        // 定义每个表的长度
        int meterLength = 12;
        int totalMeters = meterArray.length / meterLength;
        QianBaoTongModel.Meter[] meters = new QianBaoTongModel.Meter[totalMeters];
        // 提取表
        for (int i = 0; i < totalMeters; i++) {
            QianBaoTongModel.Meter meter = new QianBaoTongModel.Meter();
            byte[] arr = Arrays.copyOfRange(meterArray, i * meterLength, (i + 1) * meterLength);
            byte collectorIndex = arr[0];
            meter.setCollectorIndex(collectorIndex);
            byte[] meterAddrRaw = Arrays.copyOfRange(arr, 1, 7);
            String meterAddr = Hex.encodeHexString(reverse(meterAddrRaw));
            meter.setMeterAddrRaw(meterAddrRaw);
            meter.setMeterAddr(meterAddr);
            byte[] meterReadingNumberRaw = Arrays.copyOfRange(arr, 7, 11);
            BigDecimal meterReadingNumber = new BigDecimal(Hex.encodeHexString(reverse(meterAddrRaw))).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
            meter.setMeterReadingNumber(meterReadingNumber);
            meter.setMeterReadingNumberRaw(meterReadingNumberRaw);
            byte meterStatusRaw = arr[11];
            meter.setMeterStatusRaw(meterStatusRaw);
            meter.setMeterStatus(QianBaoTongModel.CommunicationStatus.getStatus(meterStatusRaw));
            meters[i] = meter;
        }
        return meters;
    }

    /**
     * 将表信息实体类数组转换为bcd字节数组
     *
     * @param meterArray 表信息实体类数组
     * @return bcd字节数组
     */
    private static byte[] convertMetersToByteArray(QianBaoTongModel.Meter[] meterArray) {
        // 定义每个表所占字节
        int meterLength = 12;
        int totalLength = meterArray.length * meterLength;
        byte[] meters = new byte[totalLength];
        // 转换成字节数组
        for (int i = 0; i < meterArray.length; i++) {
            meters[i * meterLength] = meterArray[i].getCollectorIndex();
            System.arraycopy(reverse(convertToBcd(meterArray[i].getMeterAddr())), 0, meters, i * meterLength + 1, 6);
        }
        return meters;
    }

    /**
     * 提取集中器信息
     *
     * @param concentratorArray 集中器信息字节数组
     * @return 集中器信息实体类数组
     */
    private static QianBaoTongModel.Concentrator extractConcentrator(byte[] concentratorArray) {
        // 定义每个表的长度
        QianBaoTongModel.Concentrator concentrator = new QianBaoTongModel.Concentrator();
        byte[] atInstruction = Arrays.copyOfRange(concentratorArray, 0, 32);
        concentrator.setAtInstruction(atInstruction);
        byte[] timingReadingRaw = Arrays.copyOfRange(concentratorArray, 32, 48);
        concentrator.setTimingReadingRaw(timingReadingRaw);
        concentrator.setTimingReading(StringUtils.newStringUsAscii(timingReadingRaw));
        byte[] clockRaw = Arrays.copyOfRange(concentratorArray, 48, 64);
        concentrator.setClockRaw(clockRaw);
        concentrator.setClock(StringUtils.newStringUsAscii(clockRaw));
        byte[] historyRecordsRaw = Arrays.copyOfRange(concentratorArray, 64, 256);
        concentrator.setHistoryRecordsRaw(historyRecordsRaw);
        int singleRecordLength = 16;
        int totalRecords = historyRecordsRaw.length / singleRecordLength;
        String[] records = new String[totalRecords];
        for (int i = 0; i < totalRecords; i++) {
            records[i] = StringUtils.newStringUsAscii(Arrays.copyOfRange(historyRecordsRaw, i * singleRecordLength, (i + 1) * singleRecordLength));
        }
        concentrator.setHistoryRecords(records);
        byte[] remarkRaw = Arrays.copyOfRange(concentratorArray, 256, 384);
        concentrator.setRemarkRaw(remarkRaw);
        concentrator.setRemark(StringUtils.newStringUsAscii(remarkRaw));
        return concentrator;
    }
}
